/**
 * Copyright (C) 2011-2012 GreenSoft S.A.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *        http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ar.com.greensoft.web.jsf;

import java.beans.Introspector;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;

import org.ajax4jsf.model.ExtendedDataModel;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.Logger;
import org.richfaces.component.SortOrder;

import ar.com.greensoft.jeecommons.web.jsf.util.JSFMenuUtil;
import ar.com.greensoft.jeecommons.web.jsf.util.ResourceBundleUtil;
import ar.com.greensoft.neosystem.crm.web.beans.NavigationConstants;
import ar.com.greensoft.neosystem.dao.DaoException;
import ar.com.greensoft.neosystem.dao.GenericDao;
import ar.com.greensoft.neosystem.dao.searching.EntityDto;
import ar.com.greensoft.neosystem.dao.searching.SortCriteria;
import ar.com.greensoft.neosystem.search.SearchFilter;
import ar.com.greensoft.web.jsf.searching.EntityDataModel;

/**
 * 
 * @author Jorge E. Villaverde
 * @date 05/07/2013
 * @version 1.0.0
 */
public abstract class AbstractFacadeBean<T extends EntityDto> 
	implements Serializable, NavigationConstants {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	protected static final Logger logger = Logger.getLogger(AbstractFacadeBean.class);
	
	// Constants ----------------------------------------------------------------------------------
	/**
	 * Appended to the entity name to conform the error name for repeated entities.
	 */
	protected static final String REPEATED_ENTITY_ERROR_MESSAGE_SUFIX = "RepeatedEntityErrorMessage";	
	/**
	 * Appended to the entity name to conform the information name for removed entities.
	 */
	protected static final String REMOVED_MESSAGE_SUFIX = "RemovedMessage";	
	/**
	 * Appended to the entity name to conform the information name for updated entities.
	 */
	protected static final String UPDATED_MESSAGE_SUFIX = "UpdatedMessage";
	/**
	 * Appended to the entity name to conform the information name for added entities.
	 */	
	protected static final String ADDED_MESSAGE_SUFIX = "AddedMessage";	
	/**
	 * Default validation error used in the application
	 */
	protected static final String DEFAULT_VALIDATION_MESSAGE = "ar.com.greensoft.validation.error";
	/**
	 * Sort Properties name
	 */
	private static final String SORT_PROPERTY_PARAMETER = "sortProperty";
	
	// Properties ---------------------------------------------------------------------------------

	/**
	 * <code>true</code> if editing an existing entity. <code>false</code> otherwise.
	 */
	private boolean editing = false;
	/**
	 * Facaded Entity
	 */
	private T entity;
	/**
	 * Entity Primary Key 
	 */
	private Long id;
	/**
	 * Mensaje de Validación a mostrar al usuario
	 */
	private String validationErrorMessage;
	
    /**
	 * Search Filter
	 */
	protected SearchFilter filter;

	/**
	 * Data Model
	 */
	private EntityDataModel<T> dataModel;
	/**
	 * Sort Orders
	 */
	private Map<String, SortOrder> sortsOrders;
	/**
	 * Sort Priorities
	 */
	private List<String> sortPriorities;
	/**
	 * Enable Multible Sorting
	 */
	private boolean multipleSorting = false;
	
	// Dependencies -------------------------------------------------------------------------------
		
	// Actions ------------------------------------------------------------------------------------
	
	/**
	 * Bean Initialization Method
	 */
	@PostConstruct
	private void init(){
		this.id = null;
		this.editing = false;
		this.sortsOrders = new HashMap<String, SortOrder>();
		this.sortPriorities = new ArrayList<String>();
		setEntity(newEntity());
	}

	/**
	 * Called when before rendering the view
	 */
	public void preRenderView(){
		if (!FacesContext.getCurrentInstance().isPostback()){
	    	GenericDao<T, Long> dao = getDao();
			if(dao == null)
				throw new java.lang.IllegalStateException("DAO not Injected");
			try {
				this.entity = dao.findById(id);
				this.editing = true;
			} catch (DaoException e) {
				logger.error(e.getMessage());
			}
		}
	}
	
	/**
	 * Called by the view to persist the entity.
	 * @return view name after persist. 
	 */
	public String persist() {
		beforePersist();
		
    	GenericDao<T, Long> dao = getDao();
		if(dao == null)
			throw new java.lang.IllegalStateException("DAO not Injected");

		try{
			if(validateEntity()){
				if(editing){
					beforeUpdate();
					dao.merge(entity);
					afterUpdate();
				}else{
					beforeCreate();
					if(validateBeforCreate()){
						dao.persist(entity);					
						afterCreate();
					}else{
						validateError();
						return currentView();
					}
				}
			}else{
				if(!StringUtils.isEmpty(validationErrorMessage)){
					JSFMenuUtil.addErrorMessage(this.validationErrorMessage);
				}else{
					final String errorMessage = ResourceBundleUtil.getValidationMessageString(DEFAULT_VALIDATION_MESSAGE);
					JSFMenuUtil.addErrorMessage(errorMessage);
				}
			}
		}catch (Exception e) {
			JSFMenuUtil.addErrorMessage(e.getLocalizedMessage());			
			return currentView();
		}	
		
		afterPersist();
		
		return persistOk();
    }
	
	
	public void create(){
    	if(logger.isTraceEnabled())
    		logger.info("Create called");
		this.entity = newEntity();
	}
	
	/**
	 * Called by the view to cancel the current operation
	 * @return name of the view when canceled the operation.
	 */
	public String cancel() {
		return persistCancel();
	}

	/* Searcheable Bean */
	
	public SearchFilter getFilter() {
		if(filter == null)
			this.filter = createSearchFilter();
		return filter;
	}

	public void search() {
		if(filter != null)
			filter.setSearchActive(true);
		if(this.dataModel != null)
			this.dataModel.reset();
	}
	
	public boolean isSearchActive() {
		if(filter == null)
			return false;
		return filter.isSearchActive();
	}
	
	public ExtendedDataModel<T> getDataModel() {
		if(this.dataModel == null){
			this.dataModel = new EntityDataModel<T>(getDao(), getFilter());
		}
		return dataModel;
	}
	
	public void sort() {
        String property = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get(SORT_PROPERTY_PARAMETER);
        if (property != null) {
            SortOrder currentPropertySortOrder = sortsOrders.get(property);
            if (multipleSorting) {
                if (!sortPriorities.contains(property)) {
                    sortPriorities.add(property);
                }
            } else {
                sortsOrders.clear();
            }
            if (currentPropertySortOrder == null || currentPropertySortOrder.equals(SortOrder.descending)) {
                sortsOrders.put(property, SortOrder.ascending);
            } else {
                sortsOrders.put(property, SortOrder.descending);
            }
            this.dataModel.reset();
            this.dataModel.setSortCriterias(createSortCriteria());
        }
    }
 
    private List<SortCriteria> createSortCriteria() {
    	List<SortCriteria> ret = new ArrayList<SortCriteria>(sortsOrders.keySet().size());
    	for(String key : sortsOrders.keySet()){
    		SortOrder order = sortsOrders.get(key);
    		ret.add(new SortCriteria(key, order.equals(SortOrder.ascending)));
    	}
		return ret;
	}
	
    public void modeChanged(ValueChangeEvent event) {
        reset();
    }
 
    public void reset() {
        sortPriorities.clear();
        sortsOrders.clear();
    }
 	
	protected String currentView() {
		return FacesContext.getCurrentInstance().getViewRoot().getViewId();
	}
	
	protected String getEntityRepeteadMessage() {
		return getEntityMessagePrefix() + REPEATED_ENTITY_ERROR_MESSAGE_SUFIX;
	}

	protected String getEntityAddedMessage() {
		return getEntityMessagePrefix() + ADDED_MESSAGE_SUFIX;
	}

	protected String getEntityUpdatedMessage() {
		return getEntityMessagePrefix() + UPDATED_MESSAGE_SUFIX;
	}

	protected String getEntityRemovedMessage() {
		return getEntityMessagePrefix() + REMOVED_MESSAGE_SUFIX;
	}

	protected void validateError() {
		Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
		String entityRepeteadMessage = ResourceBundleUtil.getResourceBundleString(getEntityRepeteadMessage(), locale);
		JSFMenuUtil.addErrorMessage(entityRepeteadMessage);
	}

	protected boolean validateBeforCreate() {
		if(!validateEntityNoRepeated()){
			return false;
		}
		return true;
	}
	
	protected boolean validateEntityNoRepeated() {
		if(isValidateRepeatedEntityBeforeCreate()){
			T repeatedEntity = findEntity(entity);
			return (repeatedEntity == null);
		}
		return true;
	}

	protected boolean isValidateRepeatedEntityBeforeCreate() {
		return true;
	}
	
	/**
	 * Sobreescribir por si hay conflictos en Resource Bundle
	 * @return
	 */
	protected String getEntityMessagePrefix(){
		String prefix = getEntity().getClass().getSimpleName();
		return Introspector.decapitalize(prefix);
	}
	
	protected T findEntity(T entity){
		if(entity == null)
			return null;
		return findEntityByName(getEntityName(entity));
	}

	protected String persistOk() {
		return INDEX_ACTION;
	}
	 
	protected String persistCancel() {
		return INDEX_ACTION;
	}
	
	// Getters/setters ----------------------------------------------------------------------------
	
	public T getEntity() {
		return entity;
	}

	public void setEntity(T entity) {
		this.entity = entity;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}
	
	public boolean isEditing() {
		return editing;
	}

	public void setEditing(boolean editing) {
		this.editing = editing;
	}

    public boolean isMultipleSorting() {
        return multipleSorting;
    }
 
    public void setMultipleSorting(boolean multipleSorting) {
        this.multipleSorting = multipleSorting;
    }
 
    public List<String> getSortPriorities() {
        return sortPriorities;
    }
 
    public Map<String, SortOrder> getSortsOrders() {
        return sortsOrders;
    }
	
	// Abstract Methods ---------------------------------------------------------------------------
	/**
	 * DAO utilizada para realizar el CRUD en el Bean.
	 * @return Subclase de {@link GenericDao} utilizado por la subclase.
	 */
	protected abstract GenericDao<T, Long> getDao();
	
	/**
	 * Factory Method para crear una nueva entidad del tipo <code>T</code>.
	 * @return Nueva entidad del Tipo <code>T</code>.
	 */
	protected abstract T newEntity();
	
	/**
	 * Factory Method para crear el filtro de búsqueda para este tipo de entidades.
	 * @return 
	 */
	protected abstract SearchFilter createSearchFilter();
		
	/**
	 * @param entity
	 * @return Nombre de la Entidad a persistir
	 */
	protected abstract String getEntityName(T entity);
	
	/**
	 * Busca una Entidad por su Nombre.
	 * <p>Se utiliza para determinar o prevenir el ingreso repetido
	 * de entidades en el sistema.</p>
	 * @param entityName
	 * @return
	 */
	protected abstract T findEntityByName(String entityName);

	// Hook methods for Update --------------------------------------------------------------------

	protected boolean validateEntity(){
		this.validationErrorMessage = null;
		return true;
	}
	
	// Hook methods for Persistence ---------------------------------------------------------------
	protected void beforePersist(){	
	}
	protected void afterPersist(){
	}
	
	// Hook methods for Create

	protected void beforeCreate(){	
	}
	protected void afterCreate(){
		FacesContext context = FacesContext.getCurrentInstance();
		Locale locale = context.getViewRoot().getLocale();
		String entityAddedMessage = ResourceBundleUtil.getResourceBundleString(getEntityAddedMessage(), locale);
		JSFMenuUtil.addInfoMessage(entityAddedMessage);
	}

	// Hook methods for Update

	protected void beforeUpdate(){	
	}
	protected void afterUpdate(){
		FacesContext context = FacesContext.getCurrentInstance();
		Locale locale = context.getViewRoot().getLocale();
		String entityAddedMessage = ResourceBundleUtil.getResourceBundleString(getEntityUpdatedMessage(), locale);
		JSFMenuUtil.addInfoMessage(entityAddedMessage);
	}	
}
